home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2008 October / PCgo 2008-10 (DVD).iso / interface / contents / vollversionen_6617 / 21733 / files / xulrunner / components / nsLivemarkService.js < prev    next >
Encoding:
Text File  |  2008-08-20  |  35.3 KB  |  1,106 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  2.  * ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License
  6.  * Version 1.1 (the "License"); you may not use this file except in
  7.  * compliance with the License. You may obtain a copy of the License
  8.  * at http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS"
  11.  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
  12.  * the License for the specific language governing rights and
  13.  * limitations under the License.
  14.  *
  15.  * The Original Code is the Places JS Livemark Service.
  16.  *
  17.  * The Initial Developer of the Original Code is Mozilla Corporation.
  18.  * Portions created by the Initial Developer are Copyright (C) 2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Annie Sullivan <annie.sullivan@gmail.com> (C++ author)
  23.  *   Joe Hughes <joe@retrovirus.com>
  24.  *   Vladimir Vukicevic <vladimir@pobox.com>
  25.  *   Masayuki Nakano <masayuki@d-toybox.com>
  26.  *   Robert Sayre <sayrer@gmail.com> (JS port)
  27.  *   Phil Ringnalda <philringnalda@gmail.com>
  28.  *   Marco Bonardo <mak77@supereva.it>
  29.  *
  30.  * Alternatively, the contents of this file may be used under the
  31.  * terms of either the GNU General Public License Version 2 or later
  32.  * (the "GPL"), or the GNU Lesser General Public License Version 2.1
  33.  * or later (the "LGPL"), in which case the provisions of the GPL or
  34.  * the LGPL are applicable instead of those above. If you wish to
  35.  * allow use of your version of this file only under the terms of
  36.  * either the GPL or the LGPL, and not to allow others to use your
  37.  * version of this file under the terms of the MPL, indicate your
  38.  * decision by deleting the provisions above and replace them with the
  39.  * notice and other provisions required by the GPL or the LGPL. If you
  40.  * do not delete the provisions above, a recipient may use your
  41.  * version of this file under the terms of any one of the MPL, the GPL
  42.  * or the LGPL.
  43.  *
  44.  * ***** END LICENSE BLOCK ***** */
  45.  
  46. const Cc = Components.classes;
  47. const Ci = Components.interfaces;
  48. const Cr = Components.results;
  49.  
  50. //@line 36 "e:\builds\tinderbox\XR-Trunk\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\content\moz\lang.js"
  51.  
  52.  
  53. /**
  54.  * lang.js - Some missing JavaScript language features
  55.  */
  56.  
  57. /**
  58.  * Partially applies a function to a particular "this object" and zero or
  59.  * more arguments. The result is a new function with some arguments of the first
  60.  * function pre-filled and the value of |this| "pre-specified".
  61.  *
  62.  * Remaining arguments specified at call-time are appended to the pre-
  63.  * specified ones.
  64.  *
  65.  * Usage:
  66.  * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2");
  67.  * barMethBound("arg3", "arg4");
  68.  *
  69.  * @param fn {string} Reference to the function to be bound
  70.  *
  71.  * @param self {object} Specifies the object which |this| should point to
  72.  * when the function is run. If the value is null or undefined, it will default
  73.  * to the global object.
  74.  *
  75.  * @returns {function} A partially-applied form of the speficied function.
  76.  */
  77. function BindToObject(fn, self, opt_args) {
  78.   var boundargs = fn.boundArgs_ || [];
  79.   boundargs = boundargs.concat(Array.slice(arguments, 2, arguments.length));
  80.  
  81.   if (fn.boundSelf_)
  82.     self = fn.boundSelf_;
  83.   if (fn.boundFn_)
  84.     fn = fn.boundFn_;
  85.  
  86.   var newfn = function() {
  87.     // Combine the static args and the new args into one big array
  88.     var args = boundargs.concat(Array.slice(arguments));
  89.     return fn.apply(self, args);
  90.   }
  91.  
  92.   newfn.boundArgs_ = boundargs;
  93.   newfn.boundSelf_ = self;
  94.   newfn.boundFn_ = fn;
  95.  
  96.   return newfn;
  97. }
  98.  
  99. /**
  100.  * Inherit the prototype methods from one constructor into another.
  101.  *
  102.  * Usage:
  103.  *
  104.  * function ParentClass(a, b) { }
  105.  * ParentClass.prototype.foo = function(a) { }
  106.  *
  107.  * function ChildClass(a, b, c) {
  108.  *   ParentClass.call(this, a, b);
  109.  * }
  110.  *
  111.  * ChildClass.inherits(ParentClass);
  112.  *
  113.  * var child = new ChildClass("a", "b", "see");
  114.  * child.foo(); // works
  115.  *
  116.  * In addition, a superclass' implementation of a method can be invoked
  117.  * as follows:
  118.  *
  119.  * ChildClass.prototype.foo = function(a) {
  120.  *   ChildClass.superClass_.foo.call(this, a);
  121.  *   // other code
  122.  * };
  123.  */
  124. Function.prototype.inherits = function(parentCtor) {
  125.   var tempCtor = function(){};
  126.   tempCtor.prototype = parentCtor.prototype;
  127.   this.superClass_ = parentCtor.prototype;
  128.   this.prototype = new tempCtor();
  129. }
  130. //@line 36 "e:\builds\tinderbox\XR-Trunk\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\content\moz\observer.js"
  131.  
  132.  
  133. // A couple of classes to simplify creating observers. 
  134. //
  135. // // Example1:
  136. //
  137. // function doSomething() { ... }
  138. // var observer = new G_ObserverWrapper(topic, doSomething);
  139. // someObj.addObserver(topic, observer);
  140. //
  141. // // Example2: 
  142. //
  143. // function doSomething() { ... }
  144. // new G_ObserverServiceObserver("profile-after-change", 
  145. //                               doSomething,
  146. //                               true /* run only once */);
  147.  
  148.  
  149. /**
  150.  * This class abstracts the admittedly simple boilerplate required of
  151.  * an nsIObserver. It saves you the trouble of implementing the
  152.  * indirection of your own observe() function.
  153.  *
  154.  * @param topic String containing the topic the observer will filter for
  155.  *
  156.  * @param observeFunction Reference to the function to call when the 
  157.  *                        observer fires
  158.  *
  159.  * @constructor
  160.  */
  161. function G_ObserverWrapper(topic, observeFunction) {
  162.   this.debugZone = "observer";
  163.   this.topic_ = topic;
  164.   this.observeFunction_ = observeFunction;
  165. }
  166.  
  167. /**
  168.  * XPCOM
  169.  */
  170. G_ObserverWrapper.prototype.QueryInterface = function(iid) {
  171.   if (iid.equals(Ci.nsISupports) || iid.equals(Ci.nsIObserver))
  172.     return this;
  173.   throw Components.results.NS_ERROR_NO_INTERFACE;
  174. }
  175.  
  176. /**
  177.  * Invoked by the thingy being observed
  178.  */
  179. G_ObserverWrapper.prototype.observe = function(subject, topic, data) {
  180.   if (topic == this.topic_)
  181.     this.observeFunction_(subject, topic, data);
  182. }
  183.  
  184.  
  185. /**
  186.  * This class abstracts the admittedly simple boilerplate required of
  187.  * observing an observerservice topic. It implements the indirection
  188.  * required, and automatically registers to hear the topic.
  189.  *
  190.  * @param topic String containing the topic the observer will filter for
  191.  *
  192.  * @param observeFunction Reference to the function to call when the 
  193.  *                        observer fires
  194.  *
  195.  * @param opt_onlyOnce Boolean indicating if the observer should unregister
  196.  *                     after it has fired
  197.  *
  198.  * @constructor
  199.  */
  200. function G_ObserverServiceObserver(topic, observeFunction, opt_onlyOnce) {
  201.   this.debugZone = "observerserviceobserver";
  202.   this.topic_ = topic;
  203.   this.observeFunction_ = observeFunction;
  204.   this.onlyOnce_ = !!opt_onlyOnce;
  205.   
  206.   this.observer_ = new G_ObserverWrapper(this.topic_, 
  207.                                          BindToObject(this.observe_, this));
  208.   this.observerService_ = Cc["@mozilla.org/observer-service;1"]
  209.                           .getService(Ci.nsIObserverService);
  210.   this.observerService_.addObserver(this.observer_, this.topic_, false);
  211. }
  212.  
  213. /**
  214.  * Unregister the observer from the observerservice
  215.  */
  216. G_ObserverServiceObserver.prototype.unregister = function() {
  217.   this.observerService_.removeObserver(this.observer_, this.topic_);
  218.   this.observerService_ = null;
  219. }
  220.  
  221. /**
  222.  * Invoked by the observerservice
  223.  */
  224. G_ObserverServiceObserver.prototype.observe_ = function(subject, topic, data) {
  225.   this.observeFunction_(subject, topic, data);
  226.   if (this.onlyOnce_)
  227.     this.unregister();
  228. }
  229.  
  230. //@line 36 "e:\builds\tinderbox\XR-Trunk\WINNT_5.2_Depend\mozilla\toolkit\components\url-classifier\content\moz\alarm.js"
  231.  
  232.  
  233. // An Alarm fires a callback after a certain amount of time, or at
  234. // regular intervals. It's a convenient replacement for
  235. // setTimeout/Interval when you don't want to bind to a specific
  236. // window.
  237. //
  238. // The ConditionalAlarm is an Alarm that cancels itself if its callback 
  239. // returns a value that type-converts to true.
  240. //
  241. // Example:
  242. //
  243. //  function foo() { dump('hi'); };
  244. //  new G_Alarm(foo, 10*1000);                   // Fire foo in 10 seconds
  245. //  new G_Alarm(foo, 10*1000, true /*repeat*/);  // Fire foo every 10 seconds
  246. //  new G_Alarm(foo, 10*1000, true, 7);          // Fire foo every 10 seconds
  247. //                                               // seven times
  248. //  new G_ConditionalAlarm(foo, 1000, true); // Fire every sec until foo()==true
  249. //
  250. //  // Fire foo every 10 seconds until foo returns true or until it fires seven
  251. //  // times, whichever happens first.
  252. //  new G_ConditionalAlarm(foo, 10*1000, true /*repeating*/, 7);
  253. //
  254. // TODO: maybe pass an isFinal flag to the callback if they opted to
  255. // set maxTimes and this is the last iteration?
  256.  
  257.  
  258. /**
  259.  * Set an alarm to fire after a given amount of time, or at specific 
  260.  * intervals.
  261.  *
  262.  * @param callback Function to call when the alarm fires
  263.  * @param delayMS Number indicating the length of the alarm period in ms
  264.  * @param opt_repeating Boolean indicating whether this should fire 
  265.  *                      periodically
  266.  * @param opt_maxTimes Number indicating a maximum number of times to 
  267.  *                     repeat (obviously only useful when opt_repeating==true)
  268.  */
  269. function G_Alarm(callback, delayMS, opt_repeating, opt_maxTimes) {
  270.   this.debugZone = "alarm";
  271.   this.callback_ = callback;
  272.   this.repeating_ = !!opt_repeating;
  273.   this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  274.   var type = opt_repeating ? 
  275.              this.timer_.TYPE_REPEATING_SLACK : 
  276.              this.timer_.TYPE_ONE_SHOT;
  277.   this.maxTimes_ = opt_maxTimes ? opt_maxTimes : null;
  278.   this.nTimes_ = 0;
  279.  
  280.   this.observerServiceObserver_ = new G_ObserverServiceObserver(
  281.                                         'xpcom-shutdown',
  282.                                         BindToObject(this.cancel, this));
  283.  
  284.   // Ask the timer to use nsITimerCallback (.notify()) when ready
  285.   this.timer_.initWithCallback(this, delayMS, type);
  286. }
  287.  
  288. /**
  289.  * Cancel this timer 
  290.  */
  291. G_Alarm.prototype.cancel = function() {
  292.   if (!this.timer_) {
  293.     return;
  294.   }
  295.  
  296.   this.timer_.cancel();
  297.   // Break circular reference created between this.timer_ and the G_Alarm
  298.   // instance (this)
  299.   this.timer_ = null;
  300.   this.callback_ = null;
  301.  
  302.   // We don't need the shutdown observer anymore
  303.   this.observerServiceObserver_.unregister();
  304. }
  305.  
  306. /**
  307.  * Invoked by the timer when it fires
  308.  * 
  309.  * @param timer Reference to the nsITimer which fired (not currently 
  310.  *              passed along)
  311.  */
  312. G_Alarm.prototype.notify = function(timer) {
  313.   // fire callback and save results
  314.   var ret = this.callback_();
  315.   
  316.   // If they've given us a max number of times to fire, enforce it
  317.   this.nTimes_++;
  318.   if (this.repeating_ && 
  319.       typeof this.maxTimes_ == "number" 
  320.       && this.nTimes_ >= this.maxTimes_) {
  321.     this.cancel();
  322.   } else if (!this.repeating_) {
  323.     // Clear out the callback closure for TYPE_ONE_SHOT timers
  324.     this.cancel();
  325.   }
  326.   // We don't cancel/cleanup timers that repeat forever until either
  327.   // xpcom-shutdown occurs or cancel() is called explicitly.
  328.  
  329.   return ret;
  330. }
  331.  
  332. G_Alarm.prototype.setDelay = function(delay) {
  333.   this.timer_.delay = delay;
  334. }
  335.  
  336. /**
  337.  * XPCOM cruft
  338.  */
  339. G_Alarm.prototype.QueryInterface = function(iid) {
  340.   if (iid.equals(Components.interfaces.nsISupports) ||
  341.       iid.equals(Components.interfaces.nsITimerCallback))
  342.     return this;
  343.  
  344.   throw Components.results.NS_ERROR_NO_INTERFACE;
  345. }
  346.  
  347.  
  348. /**
  349.  * An alarm with the additional property that it cancels itself if its 
  350.  * callback returns true.
  351.  *
  352.  * For parameter documentation, see G_Alarm
  353.  */
  354. function G_ConditionalAlarm(callback, delayMS, opt_repeating, opt_maxTimes) {
  355.   G_Alarm.call(this, callback, delayMS, opt_repeating, opt_maxTimes);
  356.   this.debugZone = "conditionalalarm";
  357. }
  358.  
  359. G_ConditionalAlarm.inherits(G_Alarm);
  360.  
  361. /**
  362.  * Invoked by the timer when it fires
  363.  * 
  364.  * @param timer Reference to the nsITimer which fired (not currently 
  365.  *              passed along)
  366.  */
  367. G_ConditionalAlarm.prototype.notify = function(timer) {
  368.   // Call G_Alarm::notify
  369.   var rv = G_Alarm.prototype.notify.call(this, timer);
  370.  
  371.   if (this.repeating_ && rv) {
  372.     G_Debug(this, "Callback of a repeating alarm returned true; cancelling.");
  373.     this.cancel();
  374.   }
  375. }
  376. //@line 53 "e:\builds\tinderbox\XR-Trunk\WINNT_5.2_Depend\mozilla\toolkit\components\places\src\nsLivemarkService.js"
  377.  
  378. function LOG(str) {
  379.   dump("*** " + str + "\n");
  380. }
  381.  
  382. const LS_CLASSID = Components.ID("{dca61eb5-c7cd-4df1-b0fb-d0722baba251}");
  383. const LS_CLASSNAME = "Livemark Service";
  384. const LS_CONTRACTID = "@mozilla.org/browser/livemark-service;2";
  385.  
  386. const PLACES_BUNDLE_URI = "chrome://places/locale/places.properties";
  387. const DEFAULT_LOAD_MSG = "Live Bookmark loading...";
  388. const DEFAULT_FAIL_MSG = "Live Bookmark feed failed to load.";
  389. const LMANNO_FEEDURI = "livemark/feedURI";
  390. const LMANNO_SITEURI = "livemark/siteURI";
  391. const LMANNO_EXPIRATION = "livemark/expiration";
  392. const LMANNO_LOADFAILED = "livemark/loadfailed";
  393.  
  394. const PS_CONTRACTID = "@mozilla.org/preferences-service;1";
  395. const NH_CONTRACTID = "@mozilla.org/browser/nav-history-service;1";
  396. const AS_CONTRACTID = "@mozilla.org/browser/annotation-service;1";
  397. const OS_CONTRACTID = "@mozilla.org/observer-service;1";
  398. const SB_CONTRACTID = "@mozilla.org/intl/stringbundle;1";
  399. const IO_CONTRACTID = "@mozilla.org/network/io-service;1";
  400. const BMS_CONTRACTID = "@mozilla.org/browser/nav-bookmarks-service;1";
  401. const FAV_CONTRACTID = "@mozilla.org/browser/favicon-service;1";
  402. const LG_CONTRACTID = "@mozilla.org/network/load-group;1";
  403. const FP_CONTRACTID = "@mozilla.org/feed-processor;1";
  404. const SEC_CONTRACTID = "@mozilla.org/scriptsecuritymanager;1";
  405. const IS_CONTRACTID = "@mozilla.org/widget/idleservice;1";
  406. const SEC_FLAGS = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
  407. const NS_BINDING_ABORTED = 0x804b0002;
  408.  
  409. // Expire livemarks after 1 hour by default
  410. var gExpiration = 3600000;
  411.  
  412. // Expire livemarks after 10 minutes on error
  413. const ERROR_EXPIRATION = 600000;
  414.  
  415. // Don't check when the user is idle for longer than half an hour
  416. const IDLE_TIMELIMIT = 1800000;
  417.  
  418. // We should check for expiration _at least_ every hour
  419. // This cap is used only if the user sets a very high expiration time (>4h)
  420. const MAX_REFRESH_TIME = 3600000;
  421.  
  422. var gIoService = Cc[IO_CONTRACTID].getService(Ci.nsIIOService);
  423. var gStringBundle;
  424. function GetString(name)
  425. {
  426.   try {
  427.     if (!gStringBundle) {
  428.       var bundleService = Cc[SB_CONTRACTID].getService();
  429.       bundleService = bundleService.QueryInterface(Ci.nsIStringBundleService);
  430.       gStringBundle = bundleService.createBundle(PLACES_BUNDLE_URI);
  431.     }
  432.  
  433.     if (gStringBundle)
  434.       return gStringBundle.GetStringFromName(name);
  435.   } catch (ex) {
  436.     LOG("Exception loading string bundle: " + ex.message);
  437.   }
  438.  
  439.   return null;
  440. }
  441.  
  442. function MarkLivemarkLoadFailed(aFolderId) {
  443.   // if it failed before, too, nothing more to do
  444.   var ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  445.   if (ans.itemHasAnnotation(aFolderId, LMANNO_LOADFAILED))
  446.     return;
  447.  
  448.   var failedMsg = GetString("bookmarksLivemarkFailed") || DEFAULT_FAIL_MSG;
  449.   var failedURI = gIoService.newURI("about:livemark-failed", null, null);
  450.   var bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  451.   bms.insertBookmark(aFolderId, failedURI, 0, failedMsg);
  452.   ans.setItemAnnotation(aFolderId, LMANNO_LOADFAILED, true, 0,
  453.                         ans.EXPIRE_NEVER);
  454. }
  455.  
  456. function LivemarkService() {
  457.  
  458.   try {
  459.     var prefs = Cc[PS_CONTRACTID].getService(Ci.nsIPrefBranch);
  460.     var livemarkRefresh =
  461.       prefs.getIntPref("browser.bookmarks.livemark_refresh_seconds");
  462.     // Reset global expiration variable to reflect hidden pref (in ms)
  463.     // with a lower limit of 1 minute (60000 ms)
  464.     gExpiration = Math.max(livemarkRefresh * 1000, 60000);
  465.   }
  466.   catch (ex) { }
  467.  
  468.   // [ {folderId:, folderURI:, feedURI:, loadGroup:, locked: } ];
  469.   this._livemarks = [];
  470.  
  471.   this._loading = GetString("bookmarksLivemarkLoading") || DEFAULT_LOAD_MSG;
  472.   this._observerServiceObserver =
  473.     new G_ObserverServiceObserver('xpcom-shutdown',
  474.                                   BindToObject(this._shutdown, this),
  475.                                   true /*only once*/);
  476.  
  477.   if (IS_CONTRACTID in Cc)
  478.     this._idleService = Cc[IS_CONTRACTID].getService(Ci.nsIIdleService);
  479.  
  480.   // this is giving a reentrant getService warning in XPCShell. bug 194568.
  481.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  482.  
  483.   var livemarks = this._ans.getItemsWithAnnotation(LMANNO_FEEDURI, {});
  484.   for (var i = 0; i < livemarks.length; i++) {
  485.     var feedURI =
  486.       gIoService.newURI(
  487.         this._ans.getItemAnnotation(livemarks[i], LMANNO_FEEDURI),
  488.         null, null
  489.       );
  490.     this._pushLivemark(livemarks[i], feedURI);
  491.   }
  492.  
  493.   this._bms.addObserver(this, false);
  494. }
  495.  
  496. LivemarkService.prototype = {
  497.  
  498.   get _bms() {
  499.     if (!this.__bms)
  500.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  501.     return this.__bms;
  502.   },
  503.  
  504.   get _history() {
  505.     if (!this.__history)
  506.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  507.     return this.__history;
  508.   },
  509.  
  510.   _updateTimer: null,
  511.   start: function LS_start() {
  512.     if (this._updateTimer)
  513.       return;
  514.     // start is called in delayed startup, 5s after browser startup
  515.     // we do a first check of the livemarks here, next checks will be on timer
  516.     // browser start => 5s => this.start() => check => refresh_time => check
  517.     this._checkAllLivemarks();
  518.     // the refresh time is calculated from the expiration time, but with a cap
  519.     var refresh_time = Math.min(Math.floor(gExpiration / 4), MAX_REFRESH_TIME);
  520.     this._updateTimer = new G_Alarm(BindToObject(this._checkAllLivemarks, this),
  521.                                     refresh_time, true /* repeat */);
  522.   },
  523.  
  524.   _pushLivemark: function LS__pushLivemark(aFolderId, aFeedURI) {
  525.     // returns new length of _livemarks
  526.     return this._livemarks.push({folderId: aFolderId, feedURI: aFeedURI,
  527.                                  loadingId: -1});
  528.   },
  529.  
  530.   _getLivemarkIndex: function LS__getLivemarkIndex(aFolderId) {
  531.     for (var i = 0; i < this._livemarks.length; ++i) {
  532.       if (this._livemarks[i].folderId == aFolderId)
  533.         return i;
  534.     }
  535.     throw Cr.NS_ERROR_INVALID_ARG;
  536.   },
  537.  
  538.   _shutdown: function LS__shutdown() {
  539.     // remove bookmarks observer
  540.     this._bms.removeObserver(this);
  541.  
  542.     for (var livemark in this._livemarks) {
  543.       if (livemark.loadGroup)
  544.         livemark.loadGroup.cancel(NS_BINDING_ABORTED);
  545.     }
  546.  
  547.     // kill timer
  548.     if (this._updateTimer) {
  549.       this._updateTimer.cancel();
  550.       this._updateTimer = null;
  551.     }
  552.   },
  553.  
  554.   _checkAllLivemarks: function LS__checkAllLivemarks() {
  555.     // check if livemarks are expired, update if needed
  556.     for (var i = 0; i < this._livemarks.length; ++i) {
  557.       this._updateLivemarkChildren(i, false);
  558.     }
  559.   },
  560.  
  561.   deleteLivemarkChildren: function LS_deleteLivemarkChildren(aFolderId) {
  562.     this._bms.removeFolderChildren(aFolderId);
  563.   },
  564.  
  565.   insertLivemarkLoadingItem: function LS_insertLivemarkLoading(aBms, aLivemark) {
  566.     var loadingURI = gIoService.newURI("about:livemark-loading", null, null);
  567.     if (!aLivemark.loadingId || aLivemark.loadingId == -1)
  568.       aLivemark.loadingId = aBms.insertBookmark(aLivemark.folderId, loadingURI,
  569.                                               0, this._loading);
  570.   },
  571.  
  572.   _updateLivemarkChildren:
  573.   function LS__updateLivemarkChildren(aIndex, aForceUpdate) {
  574.     if (this._livemarks[aIndex].locked)
  575.       return false;
  576.  
  577.     var livemark = this._livemarks[aIndex];
  578.     livemark.locked = true;
  579.     try {
  580.       // Check the TTL/expiration on this.  If there isn't one,
  581.       // then we assume it's never been loaded.  We perform this
  582.       // check even when the update is being forced, in case the
  583.       // livemark has somehow never been loaded.
  584.       var expireTime = this._ans.getItemAnnotation(livemark.folderId,
  585.                                                    LMANNO_EXPIRATION);
  586.       if (!aForceUpdate && expireTime > Date.now()) {
  587.         // no need to refresh
  588.         livemark.locked = false;
  589.         return false;
  590.       }
  591.  
  592.       // Check the user idle time.
  593.       // If the user is away from the computer, don't bother updating,
  594.       // so we save some bandwidth. 
  595.       // If we can't get the idle time, assume the user is not idle.
  596.       var idleTime = 0;
  597.       try {
  598.         idleTime = this._idleService.idleTime;
  599.       }
  600.       catch (ex) { /* We don't care */ }
  601.       if (idleTime > IDLE_TIMELIMIT) {
  602.         livemark.locked = false;
  603.         return false;
  604.       }
  605.     }
  606.     catch (ex) {
  607.       // This livemark has never been loaded, since it has no expire time.
  608.     }
  609.  
  610.     var loadgroup;
  611.     try {
  612.       // Create a load group for the request.  This will allow us to
  613.       // automatically keep track of redirects, so we can always
  614.       // cancel the channel.
  615.       loadgroup = Cc[LG_CONTRACTID].createInstance(Ci.nsILoadGroup);
  616.       var uriChannel = gIoService.newChannel(livemark.feedURI.spec, null, null);
  617.       uriChannel.loadGroup = loadgroup;
  618.       uriChannel.loadFlags |= Ci.nsIRequest.LOAD_BACKGROUND |
  619.                               Ci.nsIRequest.VALIDATE_ALWAYS;
  620.       var httpChannel = uriChannel.QueryInterface(Ci.nsIHttpChannel);
  621.       httpChannel.requestMethod = "GET";
  622.       httpChannel.setRequestHeader("X-Moz", "livebookmarks", false);
  623.  
  624.       // Stream the result to the feed parser with this listener
  625.       var listener = new LivemarkLoadListener(livemark);
  626.       this.insertLivemarkLoadingItem(this._bms, livemark);
  627.       httpChannel.notificationCallbacks = listener;
  628.       httpChannel.asyncOpen(listener, null);
  629.     }
  630.     catch (ex) {
  631.       if (livemark.loadingId != -1) {
  632.         this._bms.removeItem(livemark.loadingId);
  633.         livemark.loadingId = -1;
  634.       }
  635.       MarkLivemarkLoadFailed(livemark.folderId);
  636.       livemark.locked = false;
  637.       return false;
  638.     }
  639.     livemark.loadGroup = loadgroup;
  640.     return true;
  641.   },
  642.  
  643.   createLivemark: function LS_createLivemark(aParentId, aName, aSiteURI,
  644.                                              aFeedURI, aIndex) {
  645.     // Don't add livemarks to livemarks
  646.     if (this.isLivemark(aParentId))
  647.       throw Cr.NS_ERROR_INVALID_ARG;
  648.  
  649.     var folderId = this._createFolder(aParentId, aName, aSiteURI,
  650.                                       aFeedURI, aIndex);
  651.  
  652.     // do a first update of the livemark children
  653.     this._updateLivemarkChildren(this._pushLivemark(folderId, aFeedURI) - 1,
  654.                                  false);
  655.  
  656.     return folderId;
  657.   },
  658.  
  659.   createLivemarkFolderOnly:
  660.   function LS_createLivemarkFolderOnly(aParentId, aName, aSiteURI,
  661.                                        aFeedURI, aIndex) {
  662.     // Don't add livemarks to livemarks
  663.     if (this.isLivemark(aParentId))
  664.       throw Cr.NS_ERROR_INVALID_ARG;
  665.  
  666.     var folderId = this._createFolder(aParentId, aName, aSiteURI,
  667.                                       aFeedURI, aIndex);
  668.  
  669.     var livemarkIndex = this._pushLivemark(folderId, aFeedURI) - 1;
  670.     var livemark = this._livemarks[livemarkIndex];
  671.     this.insertLivemarkLoadingItem(this._bms, livemark);
  672.  
  673.     return folderId;
  674.   },
  675.  
  676.   _createFolder:
  677.   function LS__createFolder(aParentId, aName, aSiteURI, aFeedURI, aIndex) {
  678.     var folderId = this._bms.createFolder(aParentId, aName, aIndex);
  679.     this._bms.setFolderReadonly(folderId, true);
  680.  
  681.     // Add an annotation to map the folder id to the livemark feed URI
  682.     this._ans.setItemAnnotation(folderId, LMANNO_FEEDURI, aFeedURI.spec, 0,
  683.                                 this._ans.EXPIRE_NEVER);
  684.  
  685.     if (aSiteURI) {
  686.       // Add an annotation to map the folder URI to the livemark site URI
  687.       this._setSiteURISecure(folderId, aFeedURI, aSiteURI);
  688.     }
  689.  
  690.     return folderId;
  691.   },
  692.  
  693.   isLivemark: function LS_isLivemark(aFolderId) {
  694.     return this._ans.itemHasAnnotation(aFolderId, LMANNO_FEEDURI);
  695.   },
  696.  
  697.   _ensureLivemark: function LS__ensureLivemark(aFolderId) {
  698.     if (!this.isLivemark(aFolderId))
  699.       throw Cr.NS_ERROR_INVALID_ARG;
  700.   },
  701.  
  702.   getSiteURI: function LS_getSiteURI(aFolderId) {
  703.     this._ensureLivemark(aFolderId);
  704.  
  705.     if (this._ans.itemHasAnnotation(aFolderId, LMANNO_SITEURI)) {
  706.       var siteURIString =
  707.         this._ans.getItemAnnotation(aFolderId, LMANNO_SITEURI);
  708.  
  709.       return gIoService.newURI(siteURIString, null, null);
  710.     }
  711.     return null;
  712.   },
  713.  
  714.   setSiteURI: function LS_setSiteURI(aFolderId, aSiteURI) {
  715.     this._ensureLivemark(aFolderId);
  716.  
  717.     if (!aSiteURI) {
  718.       this._ans.removeItemAnnotation(aFolderId, LMANNO_SITEURI);
  719.       return;
  720.     }
  721.  
  722.     var livemarkIndex = this._getLivemarkIndex(aFolderId);
  723.     var livemark = this._livemarks[livemarkIndex];
  724.     this._setSiteURISecure(aFolderId, livemark.feedURI, aSiteURI);
  725.   },
  726.  
  727.   _setSiteURISecure:
  728.   function LS__setSiteURISecure(aFolderId, aFeedURI, aSiteURI) {
  729.     var secMan = Cc[SEC_CONTRACTID].getService(Ci.nsIScriptSecurityManager);
  730.     var feedPrincipal = secMan.getCodebasePrincipal(aFeedURI);
  731.     try {
  732.       secMan.checkLoadURIWithPrincipal(feedPrincipal, aSiteURI, SEC_FLAGS);
  733.     }
  734.     catch (e) {
  735.       return;
  736.     }
  737.     this._ans.setItemAnnotation(aFolderId, LMANNO_SITEURI, aSiteURI.spec,
  738.                                 0, this._ans.EXPIRE_NEVER);
  739.   },
  740.  
  741.   getFeedURI: function LS_getFeedURI(aFolderId) {
  742.     if (this._ans.itemHasAnnotation(aFolderId, LMANNO_FEEDURI))
  743.       return gIoService.newURI(this._ans.getItemAnnotation(aFolderId,
  744.                                                            LMANNO_FEEDURI),
  745.                                null, null);
  746.     return null;
  747.   },
  748.  
  749.   setFeedURI: function LS_setFeedURI(aFolderId, aFeedURI) {
  750.     if (!aFeedURI)
  751.       throw Cr.NS_ERROR_INVALID_ARG;
  752.  
  753.     this._ans.setItemAnnotation(aFolderId, LMANNO_FEEDURI, aFeedURI.spec, 0,
  754.                                 this._ans.EXPIRE_NEVER);
  755.  
  756.     // now update our internal table
  757.     var livemarkIndex = this._getLivemarkIndex(aFolderId);
  758.     this._livemarks[livemarkIndex].feedURI = aFeedURI;
  759.   },
  760.  
  761.   reloadAllLivemarks: function LS_reloadAllLivemarks() {
  762.     for (var i = 0; i < this._livemarks.length; ++i) {
  763.       this._updateLivemarkChildren(i, true);
  764.     }
  765.   },
  766.  
  767.   reloadLivemarkFolder: function LS_reloadLivemarkFolder(aFolderId) {
  768.     var livemarkIndex = this._getLivemarkIndex(aFolderId);
  769.     this._updateLivemarkChildren(livemarkIndex, true);
  770.   },
  771.  
  772.   // nsINavBookmarkObserver
  773.   onBeginUpdateBatch: function() { },
  774.   onEndUpdateBatch: function() { },
  775.   onItemAdded: function() { },
  776.   onItemChanged: function() { },
  777.   onItemVisited: function() { },
  778.   onItemMoved: function() { },
  779.  
  780.   onItemRemoved: function(aItemId, aParentId, aIndex) {
  781.     // we don't need to remove annotations since itemAnnotations
  782.     // are already removed with the bookmark
  783.     try {
  784.       var livemarkIndex = this._getLivemarkIndex(aItemId);
  785.     }
  786.     catch(ex) {
  787.       // not a livemark
  788.       return;
  789.     }
  790.     var livemark = this._livemarks[livemarkIndex];
  791.  
  792.     // remove the livemark from the update array
  793.     this._livemarks.splice(livemarkIndex, 1);
  794.  
  795.     if (livemark.loadGroup)
  796.       livemark.loadGroup.cancel(NS_BINDING_ABORTED);
  797.   },
  798.  
  799.   createInstance: function LS_createInstance(aOuter, aIID) {
  800.     if (aOuter != null)
  801.       throw Cr.NS_ERROR_NO_AGGREGATION;
  802.     return this.QueryInterface(aIID);
  803.   },
  804.  
  805.   QueryInterface: function LS_QueryInterface(aIID) {
  806.     if (aIID.equals(Ci.nsILivemarkService) ||
  807.         aIID.equals(Ci.nsIFactory) ||
  808.         aIID.equals(Ci.nsINavBookmarkObserver) ||
  809.         aIID.equals(Ci.nsISupports))
  810.       return this;
  811.     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  812.   }
  813. };
  814.  
  815. function LivemarkLoadListener(aLivemark) {
  816.   this._livemark = aLivemark;
  817.   this._livemark.loadingId = -1;
  818.   this._processor = null;
  819.   this._isAborted = false;
  820.   this._ttl = gExpiration;
  821.   this._ans = Cc[AS_CONTRACTID].getService(Ci.nsIAnnotationService);
  822. }
  823.  
  824. LivemarkLoadListener.prototype = {
  825.  
  826.   abort: function LLL_abort() {
  827.     this._isAborted = true;
  828.   },
  829.  
  830.   get _bms() {
  831.     if (!this.__bms)
  832.       this.__bms = Cc[BMS_CONTRACTID].getService(Ci.nsINavBookmarksService);
  833.     return this.__bms;
  834.   },
  835.  
  836.   get _history() {
  837.     if (!this.__history)
  838.       this.__history = Cc[NH_CONTRACTID].getService(Ci.nsINavHistoryService);
  839.     return this.__history;
  840.   },
  841.  
  842.   // called back from handleResult
  843.   runBatched: function LLL_runBatched(aUserData) {
  844.     var result = aUserData.QueryInterface(Ci.nsIFeedResult);
  845.  
  846.     // We need this to make sure the item links are safe
  847.     var secMan = Cc[SEC_CONTRACTID].getService(Ci.nsIScriptSecurityManager);
  848.     var feedPrincipal = secMan.getCodebasePrincipal(this._livemark.feedURI);
  849.  
  850.     var lmService = Cc[LS_CONTRACTID].getService(Ci.nsILivemarkService);
  851.  
  852.     // Enforce well-formedness because the existing code does
  853.     if (!result || !result.doc || result.bozo) {
  854.       if (this._livemark.loadingId != -1) {
  855.         this._bms.removeItem(this._livemark.loadingId);
  856.         this._livemark.loadingId = -1;
  857.       }
  858.       MarkLivemarkLoadFailed(this._livemark.folderId);
  859.       this._ttl = gExpiration;
  860.       throw Cr.NS_ERROR_FAILURE;
  861.     }
  862.  
  863.     // Clear out any child nodes of the livemark folder, since
  864.     // they're about to be replaced.
  865.     this.deleteLivemarkChildren(this._livemark.folderId);
  866.     this._livemark.loadingId = -1;
  867.     // removeItemAnnotation can safely be used even when the anno isn't set
  868.     this._ans.removeItemAnnotation(this._livemark.folderId, LMANNO_LOADFAILED);
  869.     var feed = result.doc.QueryInterface(Ci.nsIFeed);
  870.     if (feed.link) {
  871.       var oldSiteURI = lmService.getSiteURI(this._livemark.folderId);
  872.       if (!oldSiteURI || !feed.link.equals(oldSiteURI))
  873.         lmService.setSiteURI(this._livemark.folderId, feed.link);
  874.     }
  875.     // Loop through and check for a link and a title
  876.     // as the old code did
  877.     for (var i = 0; i < feed.items.length; ++i) {
  878.       let entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
  879.       let href = entry.link;
  880.       if (!href)
  881.         continue;
  882.  
  883.       let title = entry.title ? entry.title.plainText() : entry.updated;
  884.       if (!title)
  885.         continue;
  886.  
  887.       try {
  888.         secMan.checkLoadURIWithPrincipal(feedPrincipal, href, SEC_FLAGS);
  889.       }
  890.       catch(ex) {
  891.         continue;
  892.       }
  893.  
  894.       this.insertLivemarkChild(this._livemark.folderId, href, title);
  895.     }
  896.   },
  897.  
  898.   /**
  899.    * See nsIFeedResultListener.idl
  900.    */
  901.   handleResult: function LLL_handleResult(aResult) {
  902.     if (this._isAborted) {
  903.       if (this._livemark.loadingId != -1) {
  904.         this._bms.removeItem(this._livemark.loadingId);
  905.         this._livemark.loadingId = -1;
  906.       }
  907.       MarkLivemarkLoadFailed(this._livemark.folderId);
  908.       this._livemark.locked = false;
  909.       return;
  910.     }
  911.     try {
  912.       // The actual work is done in runBatched, see above.
  913.       this._bms.runInBatchMode(this, aResult);
  914.     }
  915.     finally {
  916.       this._processor.listener = null;
  917.       this._processor = null;
  918.       this._livemark.locked = false;
  919.     }
  920.   },
  921.  
  922.   deleteLivemarkChildren: LivemarkService.prototype.deleteLivemarkChildren,
  923.  
  924.   insertLivemarkChild:
  925.   function LS_insertLivemarkChild(aFolderId, aUri, aTitle) {
  926.     this._bms.insertBookmark(aFolderId, aUri, this._bms.DEFAULT_INDEX, aTitle);
  927.   },
  928.  
  929.   /**
  930.    * See nsIStreamListener.idl
  931.    */
  932.   onDataAvailable: function LLL_onDataAvailable(aRequest, aContext, aInputStream,
  933.                                                 aSourceOffset, aCount) {
  934.     this._processor.onDataAvailable(aRequest, aContext, aInputStream,
  935.                                     aSourceOffset, aCount);
  936.   },
  937.  
  938.   /**
  939.    * See nsIRequestObserver.idl
  940.    */
  941.   onStartRequest: function LLL_onStartRequest(aRequest, aContext) {
  942.     if (this._isAborted)
  943.       throw Cr.NS_ERROR_UNEXPECTED;
  944.  
  945.     var channel = aRequest.QueryInterface(Ci.nsIChannel);
  946.  
  947.     // Parse feed data as it comes in
  948.     this._processor = Cc[FP_CONTRACTID].createInstance(Ci.nsIFeedProcessor);
  949.     this._processor.listener = this;
  950.     this._processor.parseAsync(null, channel.URI);
  951.  
  952.     this._processor.onStartRequest(aRequest, aContext);
  953.   },
  954.  
  955.   /**
  956.    * See nsIRequestObserver.idl
  957.    */
  958.   onStopRequest: function LLL_onStopRequest(aRequest, aContext, aStatus) {
  959.     if (!Components.isSuccessCode(aStatus)) {
  960.       // Something went wrong, try to load again in a bit
  961.       this._setResourceTTL(ERROR_EXPIRATION);
  962.       this._isAborted = true;
  963.       if (this._livemark.loadingId != -1) {
  964.         this._bms.removeItem(this._livemark.loadingId);
  965.         this._livemark.loadingId = -1;
  966.       }
  967.       MarkLivemarkLoadFailed(this._livemark.folderId);
  968.       this._livemark.locked = false;
  969.       return;
  970.     }
  971.     // Set an expiration on the livemark, for reloading the data
  972.     try {
  973.       this._processor.onStopRequest(aRequest, aContext, aStatus);
  974.  
  975.       // Calculate a new ttl
  976.       var channel = aRequest.QueryInterface(Ci.nsICachingChannel);
  977.       if (channel) {
  978.         var entryInfo = channel.cacheToken.QueryInterface(Ci.nsICacheEntryInfo);
  979.         if (entryInfo) {
  980.           // nsICacheEntryInfo returns value as seconds,
  981.           // expireTime stores as milliseconds
  982.           var expireTime = entryInfo.expirationTime * 1000;
  983.           var nowTime = Date.now();
  984.  
  985.           // note, expireTime can be 0, see bug 383538
  986.           if (expireTime > nowTime) {
  987.             this._setResourceTTL(Math.max((expireTime - nowTime),
  988.                                  gExpiration));
  989.             return;
  990.           }
  991.         }
  992.       }
  993.     }
  994.     catch (ex) { }
  995.     this._setResourceTTL(this._ttl);
  996.   },
  997.  
  998.   _setResourceTTL: function LLL__setResourceTTL(aMilliseconds) {
  999.     var expireTime = Date.now() + aMilliseconds;
  1000.     this._ans.setItemAnnotation(this._livemark.folderId, LMANNO_EXPIRATION,
  1001.                                 expireTime, 0,
  1002.                                 Ci.nsIAnnotationService.EXPIRE_NEVER);
  1003.   },
  1004.  
  1005.   /**
  1006.    * See nsIBadCertListener2
  1007.    */
  1008.   notifyCertProblem: function LLL_certProblem(aSocketInfo, aStatus, aTargetSite) {
  1009.     return true;
  1010.   },
  1011.  
  1012.   /**
  1013.    * See nsISSLErrorListener
  1014.    */
  1015.   notifySSLError: function LLL_SSLError(aSocketInfo, aError, aTargetSite) {
  1016.     return true;
  1017.   },
  1018.  
  1019.   /**
  1020.    * See nsIInterfaceRequestor
  1021.    */
  1022.   getInterface: function LLL_getInterface(aIID) {
  1023.     return this.QueryInterface(aIID);
  1024.   },
  1025.  
  1026.   /**
  1027.    * See nsISupports.idl
  1028.    */
  1029.   QueryInterface: function LLL_QueryInterface(aIID) {
  1030.     if (aIID.equals(Ci.nsIFeedResultListener) ||
  1031.         aIID.equals(Ci.nsIStreamListener) ||
  1032.         aIID.equals(Ci.nsIRequestObserver)||
  1033.         aIID.equals(Ci.nsINavHistoryBatchCallback) ||
  1034.         aIID.equals(Ci.nsIBadCertListener2) ||
  1035.         aIID.equals(Ci.nsISSLErrorListener) ||
  1036.         aIID.equals(Ci.nsIInterfaceRequestor) ||
  1037.         aIID.equals(Ci.nsISupports))
  1038.       return this;
  1039.     throw Cr.NS_ERROR_NO_INTERFACE;
  1040.   },
  1041. }
  1042.  
  1043. function GenericComponentFactory(aCtor) {
  1044.   this._ctor = aCtor;
  1045. }
  1046.  
  1047. GenericComponentFactory.prototype = {
  1048.  
  1049.   _ctor: null,
  1050.  
  1051.   // nsIFactory
  1052.   createInstance: function(aOuter, aIID) {
  1053.     if (aOuter != null)
  1054.       throw Cr.NS_ERROR_NO_AGGREGATION;
  1055.     return (new this._ctor()).QueryInterface(aIID);
  1056.   },
  1057.  
  1058.   // nsISupports
  1059.   QueryInterface: function(aIID) {
  1060.     if (aIID.equals(Ci.nsIFactory) ||
  1061.         aIID.equals(Ci.nsISupports))
  1062.       return this;
  1063.     throw Cr.NS_ERROR_NO_INTERFACE;
  1064.   },
  1065.  
  1066. };
  1067.  
  1068. var Module = {
  1069.   QueryInterface: function(aIID) {
  1070.     if (aIID.equals(Ci.nsIModule) ||
  1071.         aIID.equals(Ci.nsISupports))
  1072.       return this;
  1073.  
  1074.     throw Cr.NS_ERROR_NO_INTERFACE;
  1075.   },
  1076.  
  1077.   getClassObject: function M_getClassObject(aCompMgr, aCID, aIID) {
  1078.     if (!aIID.equals(Ci.nsIFactory))
  1079.       throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  1080.     if (aCID.equals(LS_CLASSID))
  1081.       return new GenericComponentFactory(LivemarkService);
  1082.  
  1083.     throw Cr.NS_ERROR_NO_INTERFACE;
  1084.   },
  1085.  
  1086.   registerSelf: function(aCompMgr, aFile, aLocation, aType) {
  1087.     var cr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1088.  
  1089.     cr.registerFactoryLocation(LS_CLASSID, LS_CLASSNAME,
  1090.       LS_CONTRACTID, aFile, aLocation, aType);
  1091.   },
  1092.  
  1093.   unregisterSelf: function M_unregisterSelf(aCompMgr, aLocation, aType) {
  1094.     var cr = aCompMgr.QueryInterface(Ci.nsIComponentRegistrar);
  1095.     cr.unregisterFactoryLocation(LS_CLASSID, aLocation);
  1096.   },
  1097.  
  1098.   canUnload: function M_canUnload(aCompMgr) {
  1099.     return true;
  1100.   }
  1101. };
  1102.  
  1103. function NSGetModule(aCompMgr, aFile) {
  1104.   return Module;
  1105. }
  1106.